package com.softWang.system.aop;

import com.softWang.common.aop.annotation.SysOperationLog;
import com.softWang.common.base.LoginUser;
import com.softWang.common.exception.ErrorCode;
import com.softWang.common.response.ResponseResult;
import com.softWang.common.utils.SecurityUtils;
import com.softWang.common.utils.TimeTool;
import com.softWang.system.po.SystemOperatorLog;
import com.softWang.system.service.SystemOperatorLogService;
import org.apache.ibatis.javassist.*;
import org.apache.ibatis.javassist.bytecode.CodeAttribute;
import org.apache.ibatis.javassist.bytecode.LocalVariableAttribute;
import org.apache.ibatis.javassist.bytecode.MethodInfo;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Date;

/**
 * author: admin
 * date: 2020/5/27
 * description: 系统操作日志，切面类
 */
@Aspect
@Component // 必须添加 @Component 或 @Service 等注解，让 Spring 管理该类,否则切面不会生效
public class SysOperationAspect {
    private Logger log = LoggerFactory.getLogger(SysOperationAspect.class);

    @Resource
    private SystemOperatorLogService systemOperatorLogService;

    @Pointcut("@annotation(com.softWang.common.aop.annotation.SysOperationLog)")
    public void logPointCut() {

    }

    @SuppressWarnings("unchecked")
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {

        long startTime = System.currentTimeMillis();
        // 执行方法
        Object result = point.proceed();
        // 保存日志
        long endTime = System.currentTimeMillis();
        saveSysLog(point, startTime, endTime);

        return result;
    }


    /**
     * 保存系统操作日志
     *
     * @param joinPoint
     */
    private void saveSysLog(JoinPoint joinPoint,long startTime, long endTime) {

        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            SysOperationLog syslog = method.getAnnotation(SysOperationLog.class);
            // 日志记录
            String opt = "";
            String model = "";
            if (syslog != null) {
                // 注解上的业务描述
                opt = syslog.value();
                // 注解上的业务所属模块
                model = syslog.model();
            }

            // 请求的方法名
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = signature.getName();
            String methodFullName = className + "." + methodName + "()";
            // 请求的参数
            String classType = joinPoint.getTarget().getClass().getName();
            Class<?> clazz = Class.forName(classType);
            String clazzName = clazz.getName();
            String[] paramNames = getFieldsName(this.getClass(), clazzName, methodName);
            String logContent = getParams(paramNames, joinPoint);

            LoginUser loginUser = SecurityUtils.getLoginUser();

            SystemOperatorLog operationLog = new SystemOperatorLog();
            operationLog.setUserid(loginUser.getUserId());
            operationLog.setUserName(loginUser.getUserName());
            operationLog.setLoginName(loginUser.getLoginName());
            operationLog.setModel(model);
            operationLog.setOptDesc(opt);
            operationLog.setReqMethod(methodFullName);
            operationLog.setReqParameters(logContent);
            long time = endTime - startTime;
            operationLog.setStarttime(TimeTool.format(new Date(startTime),"yyyy-MM-dd HH:mm:ss"));
            operationLog.setEndtime(TimeTool.format(new Date(endTime),"yyyy-MM-dd HH:mm:ss"));
            operationLog.setOptTime(String.valueOf(time));
            operationLog.setCtime(LocalDateTime.now());
            operationLog.setUserIp(loginUser.getUserIp());

            systemOperatorLogService.add(operationLog);

        } catch (Exception ex) {
            ex.printStackTrace();
            log.error("记录日志异常信息:" + ex.getMessage());
        }
    }

    private String[] types = { "java.lang.Integer", "java.lang.Double", "java.lang.Float", "java.lang.Long",
            "java.lang.Short", "java.lang.Byte", "java.lang.Boolean", "java.lang.Char", "java.lang.String", "int",
            "double", "long", "short", "byte", "boolean", "char", "float" };

    private String getParams(String[] paramNames, JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        StringBuilder sb = new StringBuilder();
        boolean clazzFlag = true;
        for (int k = 0; k < args.length; k++) {
            Object arg = args[k];
            sb.append(paramNames[k] + " ");
            if (null != arg) {
                // 获取对象类型
                String typeName = arg.getClass().getTypeName();

                for (String t : types) {
                    if (t.equals(typeName)) {
                        sb.append("=" + arg + "; ");
                    }
                }
                if (clazzFlag) {
                    sb.append(getFieldsValue(arg));
                }
            } else {
                sb.append("=;");
            }

        }
        return sb.toString();
    }

    private String getFieldsValue(Object obj) {
        Field[] fields = obj.getClass().getDeclaredFields();
        String typeName = obj.getClass().getTypeName();
        for (String t : types) {
            if (t.equals(typeName))
            {
                return "";
            }

        }
        StringBuilder sb = new StringBuilder();
        sb.append("【");
        for (Field f : fields) {
            f.setAccessible(true);
            try {
                for (String str : types) {
                    if (f.getType().getName().equals(str)) {
                        sb.append(f.getName() + " = " + f.get(obj) + "; ");
                    }
                }
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        sb.append("】");
        return sb.toString();
    }

    @SuppressWarnings("rawtypes")
    private String[] getFieldsName(Class cls, String clazzName, String methodName) throws NotFoundException {
        ClassPool pool = ClassPool.getDefault();
        // ClassClassPath classPath = new ClassClassPath(this.getClass());
        ClassClassPath classPath = new ClassClassPath(cls);
        pool.insertClassPath(classPath);

        CtClass cc = pool.get(clazzName);
        CtMethod cm = cc.getDeclaredMethod(methodName);
        MethodInfo methodInfo = cm.getMethodInfo();
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
        LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
        if (attr == null) {
            // exception
        }
        String[] paramNames = new String[cm.getParameterTypes().length];
        int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
        for (int i = 0; i < paramNames.length; i++) {
            paramNames[i] = attr.variableName(i + pos); // paramNames即参数名
        }
        return paramNames;
    }
}
